﻿using System.Collections.Immutable;
using Turnable.Characters;
using Turnable.Layouts;
using Turnable.TiledMap;

namespace Turnable.Places;

public record Stage()
{
    public CharacterLocations CharacterLocations { get; private set; } = new();
    public Map Map { get; private set; } = Map.Empty(0, 0);

    public Stage(int mapWidth, int mapHeight) : this()
    {
        Map = Map.Empty(mapWidth, mapHeight);
    }

    public Stage(string mapPath) : this()
    {
        Map = Map.Load(mapPath);
    }

    public MovementInfo IsAllowedAtLocation(Character character, Location location, CollisionMasks? collisionMasks = null)
    {
        string message = "";
        bool allowed = true;

        // Out of bounds of the map?
        if (!Map.Bounds.Contains(location))
        {
            allowed = false;
            message = $"Character '{character.Name}' cannot be moved to {location} which is out of bounds of the map";
        }

        // Already occupied by another character?
        if (CharacterLocations.Value.Any((kvp) => kvp.Value.Last() == location && !(kvp.Key == character)))
        {
            allowed = false;
            message = $"Character '{character.Name}' cannot be moved to {location} because another character is already located at {location}";
        }

        if (collisionMasks is not null)
        {
            // Occupied by an obstacle?
            if (Map.GetObstacles(collisionMasks).Contains(location))
            {
                allowed = false;
                message = $"Character '{character.Name}' cannot be moved to {location} because there is an obstacle there";
            }
        }

        return new MovementInfo(CharacterLocations[character], location, allowed, message);
    }

    public MovementInfo MoveCharacter(Character character, Location location, CollisionMasks? collisionMasks = null)
    {
        MovementInfo movementAllowedInfo = IsAllowedAtLocation(character, location, collisionMasks);

        if (movementAllowedInfo.Allowed)
        {
            CharacterLocations = CharacterLocations.Value.ContainsKey(character) ?
                new CharacterLocations(CharacterLocations.Value.SetItem(character, CharacterLocations.Value[character].Add(location)))
                : new CharacterLocations(CharacterLocations.Value.Add(character, ImmutableList.Create(location)));
        }

        return movementAllowedInfo;
    }

    public MovementInfo MoveCharacter(Character character, Direction direction, int distance, CollisionMasks? collisionMasks = null)
    {
        // NOTE: This moves the character in the direction by the distance and stops at the first obstacle/character/bounds edge
        Location from = CharacterLocations[character] ?? throw new ArgumentException($"Character '{character.Name}' is not on the map"); // TODO: Unit test second part of this condition
        Location to = from;

        foreach (int stepDistance in Enumerable.Range(0, distance + 1))
        {
            Location stepTo = from.LocationAt(direction, stepDistance);
            MovementInfo movementAllowedInfo = IsAllowedAtLocation(character, stepTo, collisionMasks);
            if (!movementAllowedInfo.Allowed)
            {
                break;
            }

            to = stepTo;
        }

        return MoveCharacter(character, to);
    }

    public Location? CurrentCharacterLocation(Character character) => CharacterLocations[character];
    public ImmutableDictionary<Character, Location> CurrentCharacterLocations() => CharacterLocations.Locations();
    public ImmutableDictionary<Character, Location> CurrentCharacterLocations(Character characterToExclude) => CharacterLocations.Locations().Remove(characterToExclude);
}